package host.routingProtocol;

import homeMadeExceptions.NetworkException;
import homeMadeExceptions.NoSuchHostException;
import homeMadeExceptions.ScatterNetException;
import host.RoutingNetworkSocket;

import java.util.HashMap;
import java.util.Vector;

import datatypes.BluetoothDeviceAddress;
import datatypes.Host;
import datatypes.LinkMode;
import datatypes.Message;
public class StandardRoutingProtocol extends RoutingNetworkSocket {

	
	/**
	 * This Standard Routing Protocol is for simulation of Discovery Protocols on homogenous Networks.
	 * 
	 * It works in this way:
	 * 
	 * 1. Device is turned on
	 * 	1.1 Set local Linkmode to NONE
	 * 	1.2 Set UP RFCOMM Server Socket
	 *  1.3 Set UP RFCOMM Client Socket
	 * 2. Inquiry Scan, for find other Devices are started
	 * 3. Try to connect Device next to mine (only possible if other device is RFCOMM_Server)
	 * 	3.1 if connection failed, try again with next possible Device (if connection failed, the Device don't know the protocol or has max Connections) 
	 *  3.2 if no connection is possible do nothing mor, wait for incoming connection
	 * 4. Ask new Connector for all connected (directly an not directly) devices
	 * 5. If there are devices in Range, not in List, try to connect these devices, too . Do so also with exchange of devicelist
	 * 6. If there is a new incoming Connection, request Devicelist from new Connected Host
	 */
	
	private enum message_type {
		SEARCH, ROUTING_INFORMATION_QUERY, NEW_DEVICE_CONNECTED, SIMPLE_DISC_MSG, BROADCAST_DISC_MSG,
		RESP_SEARCH, RESP_ROUTING_INFORMATION_QUERY
	}

	private enum connection_state {
		CONNECTED, FAILED, OPEN
	}
	
	private final int INNERCLOCKSCANINTERVALL = 16;
	private int innerClock = 0;

	private RoutingTable rT = new RoutingTable();
	
	private HashMap<Integer, Message> sendMessages = new HashMap<Integer,Message>();
	private HashMap<Integer,Integer> msgIDtoNumberLostMsg = new HashMap <Integer, Integer>();

	private Vector<BluetoothDeviceAddress> inquiryCache = null;
	private boolean connectionCompleted = false;
	
	private HashMap<String, BluetoothDeviceAddress> activeConnections = null;
	private HashMap<String, connection_state> connectionState = new HashMap<String, connection_state>();
	
	private StandardRoutingProtocol (Host host){
		super(host);
	}
	
	public static StandardRoutingProtocol getSocket(Host host){
		return new StandardRoutingProtocol(host);
	}
	
	public void enableBluetoothDevice (){
		super.enableBluetoothDevice();
		super.hciConfig_setLM(LinkMode.NONE);
		try {
			super.getClientSocket();
			super.getServerSocket();
		} catch (NetworkException e) {
			e.printStackTrace();
		}
		super.hciTool_inq();
	}
	
	public void newRemoteHostConnected (BluetoothDeviceAddress remoteHost) {
		rT.addNewDeviceToRoutingTable(remoteHost, null);
		this.askForRoutingTableInformations(remoteHost);
		this.host.getDiscoverySocket().newConnectionEstablished(remoteHost);
	}
	
	
	/**
	 * overrides a Method from RoutingNetworkSocket
	 * 
	 * this method is invoked from Miniworld if the result of the connectionprocess of the device is known
	 * in real life, the bluetooth device itself will send this signal, if a connection could be established
	 */
	public void rfcommConnectionRequestReply (BluetoothDeviceAddress pairingPartner, boolean success){
		if (success){
			rT.addNewDeviceToRoutingTable(pairingPartner, null);
			this.activeConnections.put(pairingPartner.getAddress(), pairingPartner);
			this.connectionState.put(pairingPartner.getAddress(), connection_state.CONNECTED);
			this.askForRoutingTableInformations(pairingPartner);
		} else {
			if (this.connectionState.get(pairingPartner.getAddress()) != null && this.connectionState.get(pairingPartner.getAddress()) == connection_state.OPEN){
				this.connectionState.put(pairingPartner.getAddress(), connection_state.FAILED);				
			}
			this.startConnectingProcedure();
		}
	}
	
	//an Message could not be send to target (or it is not clear if it could be send)
	public void invokeMsgLostSignal(BluetoothDeviceAddress target, Message msg) {
		//Resent Message (3 times)
		//this message was first time sent before
		if (this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()) != null && 
				this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()).intValue() == 3){
			//do nothing (Message will not send)
		} else if (this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()) == null){
			this.msgIDtoNumberLostMsg.put(msg.getMsgID_asInteger(), new Integer(1));
			super.invokeSendMessage(msg, target);				
		} else if (this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()) != null && 
			this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()).intValue() < 3){
			int lastIndex = this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()).intValue();
			lastIndex ++;
			this.msgIDtoNumberLostMsg.remove(msg.getMsgID_asInteger());
			this.msgIDtoNumberLostMsg.put(msg.getMsgID_asInteger(), new Integer(lastIndex));
			super.invokeSendMessage(msg, target);
		}
	}

	public void invokeMsgAckSignal(Message msg, BluetoothDeviceAddress target) {
		this.host.getDiscoverySocket().invokeAckForSendedMessage(msg, target);
	}
	
	/**
	 * This method is called by discovery Layer and overrides the Method from Socket
	 * 
	 * But this method is also called by the Routing Layer, in this Case, the Method 
	 * will only execute the send, the Maessage has to be prepared before
	 * @throws NoSuchHostException 
	 
	 */
	public void invokeSendMessage(Message msg, BluetoothDeviceAddress target) {
		if (msg.isDiscoveryMessage()){
			Vector<Object> rO_content = new Vector<Object>();
			
			//if Target is unknown search for Host and than send or raise Exception
			if (rT.getNextHop(target)== null && !target.isEqual(BluetoothDeviceAddress.getBroadcastAddresse())){
				try {
					throw new NoSuchHostException("Host unknown");
				} catch (NoSuchHostException e) {
					e.printStackTrace();
				}
			} else {
				if (target.isEqual(BluetoothDeviceAddress.getBroadcastAddresse())){
					rO_content.add(message_type.BROADCAST_DISC_MSG);
					rO_content.add(msg);
					for (BluetoothDeviceAddress neighbour : rT.getNeighbors()){
						RoutingObject newRO = new RoutingObject(target, rO_content);
						newRO.addHopToRoute(this.host.getBd_Addr());
						Message tmpMsg = new Message(newRO, false);
						if (this.msgIDtoNumberLostMsg.get(tmpMsg.getMsgID_asInteger()) == null || this.msgIDtoNumberLostMsg.get(tmpMsg.getMsgID_asInteger()) != 0){
							super.invokeSendMessage(tmpMsg, rT.getNextHop(neighbour));	
							this.msgIDtoNumberLostMsg.put(tmpMsg.getMsgID_asInteger(), new Integer(0));				
						}
					}					
				} else {
					rO_content.add(message_type.SIMPLE_DISC_MSG);
					rO_content.add(msg);
					RoutingObject newRO = new RoutingObject(target, rO_content);
					newRO.addHopToRoute(this.host.getBd_Addr());
					Message tmpMsg = new Message(newRO, false);
					if (this.msgIDtoNumberLostMsg.get(tmpMsg.getMsgID_asInteger()) == null || this.msgIDtoNumberLostMsg.get(tmpMsg.getMsgID_asInteger()) != 0){
						super.invokeSendMessage(tmpMsg, rT.getNextHop(target));									
						this.msgIDtoNumberLostMsg.put(tmpMsg.getMsgID_asInteger(), new Integer(0));
					}
				}
			}
		}
		//Msg is an Routing Message --> in this case the Message was Prepared already in calling function
		else {
			if (rT.getNextHop(target) != null){
				if (this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()) == null || this.msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()) != 0){
					super.invokeSendMessage(msg, rT.getNextHop(target));				
					this.msgIDtoNumberLostMsg.put(msg.getMsgID_asInteger(), new Integer(0));
				}
			}
		}
	}
	
	
	public void  onInquiryScanAnswer (Vector<BluetoothDeviceAddress> answer) {
		this.inquiryCache = answer;	
		this.startConnectingProcedure();
	}
	
	
	//This method overrides the method from the RoutingNetworkSocket
	@SuppressWarnings("unchecked")
	/**
	 * 	 * General Contruction of Messages
	 * Message
	 * 		|
	 * 		-->Content = Routing-Object
	 * 								|
	 * 								-->Vector<Object>
	 * 											|
	 * 											--> 1. Message_Type (have a look to the local enum)
	 * 											--> 2. Parameter 1 (if used by Disc Mechanism then this is a Discovery Message)
	 * 											--> 3... Further individual needed Parameters
	 * 
	 */
	public void onReceiveMessage(Message msg, BluetoothDeviceAddress sender){
		if (msg.isRoutingMessage()){
			RoutingObject tmp = (RoutingObject)msg.getContent();
			BluetoothDeviceAddress routingTarget = tmp.getTarget();
			//at first update Route Informations
			rT.updateDistanceVector(tmp.getRoute());
			
			Vector<Object> rO_content = tmp.getMessage();
			message_type type = (message_type) rO_content.get(0);
			//oh nice, a message for me :) , thank U
			if (routingTarget.isEqual(host.getBd_Addr())){
				switch (type) {
				case SIMPLE_DISC_MSG:
					super.onReceiveMessage((Message) rO_content.get(1), tmp.getInitialSender());
					break;
				case BROADCAST_DISC_MSG:
					super.onReceiveMessage((Message) rO_content.get(1), tmp.getInitialSender());
					forwardMsg(msg);
					break;
				case ROUTING_INFORMATION_QUERY:				
					 /*
					 * Special Construcion of Messages
					 * Message == msg
					 * 		|
					 * 		-->Content = Routing-Object  == tmp
					 * 								|
					 * 								-->Vector<Object>  == rO_content
					 * 											|
					 * 											--> 1. Message_Type.ROUTING_INFORMATION_QUERY
					 * 											--> 2. connectedDevice (the to Network connected Device to ask)
					 */
					this.sendRoutingTableInformations(tmp.getInitialSender());
					break;
				case RESP_ROUTING_INFORMATION_QUERY:
					/*
					 * Special Construcion of Messages
					 * Message
					 * 		|
					 * 		-->Content = Routing-Object == tmp
					 * 								|
					 * 								-->Vector<Object>  == rO_content
	 				 * 											|
	 				 * 											--> 1. Message_Type.RESP_ROUTING_INFORMATION_QUERY
	 				 * 											--> 2. HashMap<BluetoothDeviceAddress, Integer> routingTableDistances
					 */
					this.rT.updateWithRouteInformation (tmp.getInitialSender(), (HashMap<BluetoothDeviceAddress, Integer>) rO_content.get(1));
					this.sendNewDeviceConnectedToNeighbourhood(tmp.getInitialSender(), (HashMap<BluetoothDeviceAddress, Vector<BluetoothDeviceAddress>>) rO_content.get(1));
					break;
				case NEW_DEVICE_CONNECTED:	
					/*
					 * Special Construcion of Messages
					 * Message
					 * 		|
					 * 		-->Content = Routing-Object == tmp
					 * 								|
					 * 								-->Vector<Object>  == rO_content
	 				 * 											|
	 				 * 											--> 1. Message_Type.RESP_ROUTING_INFORMATION_QUERY
	 				 * 											--> 2. BluetoothDeviceAddress newDevice in Network
	 				 * 											--> 3. HashMap<BluetoothDeviceAddress, Integer> routingTableDistances
					 */
					this.rT.saveNewDevice((BluetoothDeviceAddress) rO_content.get(1),tmp.route.size()+1, sender);
					this.rT.updateWithRouteInformation ((BluetoothDeviceAddress) rO_content.get(1), (HashMap<BluetoothDeviceAddress, Integer>) rO_content.get(2));
					forwardMsg(msg);
					break;
				}				
			}
			//oh nice, a message for me, but not only for me , everybody is cheating on me :(
			else if (routingTarget.isEqual(BluetoothDeviceAddress.getBroadcastAddresse())) {
				switch (type) {
				case SIMPLE_DISC_MSG:
					super.onReceiveMessage((Message) rO_content.get(1), tmp.getInitialSender());
					break;
				case BROADCAST_DISC_MSG:
					super.onReceiveMessage((Message) rO_content.get(1), tmp.getInitialSender());
					break;
				case ROUTING_INFORMATION_QUERY:
					 /*
					 * Special Construcion of Messages
					 * Message == msg
					 * 		|
					 * 		-->Content = Routing-Object  == tmp
					 * 								|
					 * 								-->Vector<Object>  == rO_content
					 * 											|
					 * 											--> 1. Message_Type.ROUTING_INFORMATION_QUERY
					 * 											--> 2. connectedDevice (the to Network connected Device to ask)
					 */
					this.sendRoutingTableInformations(tmp.getInitialSender());
					break;
				case RESP_ROUTING_INFORMATION_QUERY:
					/*
					 * Special Construcion of Messages
					 * Message
					 * 		|
					 * 		-->Content = Routing-Object == tmp
					 * 								|
					 * 								-->Vector<Object>  == rO_content
	 				 * 											|
	 				 * 											--> 1. Message_Type.RESP_ROUTING_INFORMATION_QUERY
	 				 * 											--> 2. HashMap<BluetoothDeviceAddress, Integer> routingTableRoutes
					 */
					this.rT.updateWithRouteInformation (tmp.getInitialSender(), (HashMap<BluetoothDeviceAddress, Integer>) rO_content.get(1));
					this.sendNewDeviceConnectedToNeighbourhood(tmp.getInitialSender(), (HashMap<BluetoothDeviceAddress, Vector<BluetoothDeviceAddress>>) rO_content.get(1));
					break;
				case NEW_DEVICE_CONNECTED:
					/*
					 * Special Construcion of Messages
					 * Message
					 * 		|
					 * 		-->Content = Routing-Object == tmp
					 * 								|
					 * 								-->Vector<Object>  == rO_content
	 				 * 											|
	 				 * 											--> 1. Message_Type.RESP_ROUTING_INFORMATION_QUERY
	 				 * 											--> 2. BluetoothDeviceAddress newDevice in Network
	 				 * 											--> 3. HashMap<BluetoothDeviceAddress, Integer> routingTableDistances
					 */
					this.rT.saveNewDevice((BluetoothDeviceAddress) rO_content.get(1),tmp.route.size()+1, sender);
					this.rT.updateWithRouteInformation ((BluetoothDeviceAddress) rO_content.get(1), (HashMap<BluetoothDeviceAddress, Integer>) rO_content.get(2));
					break;
				}
				forwardMsg(msg);
			}
			//only cheating! Nobody likes me :'(
			else  {
				switch (type) {
				case SIMPLE_DISC_MSG:
					break;
				case BROADCAST_DISC_MSG:
					break;
				case NEW_DEVICE_CONNECTED:
					break;
				case ROUTING_INFORMATION_QUERY:
					break;
				case RESP_ROUTING_INFORMATION_QUERY:
					break;
				}
				forwardMsg(msg);
			}
		}
	}
	
	@Override
	public void notificationAPI() {
		//simply count up inner clock
		this.innerClock ++;
		if (this.innerClock % INNERCLOCKSCANINTERVALL == 0){
			this.startConnectingProcedure();
		}
	}

	
	/**
	 * General Contruction of Messages
	 * Message
	 * 		|
	 * 		-->Content = Routing-Object
	 * 								|
	 * 								-->Vector<Object>
	 * 											|
	 * 											--> 1. Message_Type (have a look to the local enum)
	 * 											--> 2. Parameter 1 (if used by Disc Mechanism then this is a Discovery Message)
	 * 											--> 3... Further individual needed Parameters
	 * 
	 */
	@SuppressWarnings("unchecked")
	private void forwardMsg(Message msg) {
		RoutingObject rO = (RoutingObject) msg.getContent();
		Vector<Object> rO_content = rO.getMessage();
		
		RoutingObject newRO = new RoutingObject(rO.getTarget(), rO_content);
		Vector<BluetoothDeviceAddress> newRoute = (Vector<BluetoothDeviceAddress>) rO.getRoute().clone();
		newRoute.add(this.host.getBd_Addr());
		newRO.setRoute(newRoute);
		
		Message newMsg = new Message(newRO, msg.isDiscoveryMessage(), msg.getMsgID());
		//this message was never sent before
		if (msgIDtoNumberLostMsg.get(msg.getMsgID_asInteger()) == null){
			this.invokeSendMessage(newMsg, rO.getTarget());
		}
	}

	private void startConnectingProcedure (){
		if (activeConnections == null){
			activeConnections = new HashMap<String, BluetoothDeviceAddress>();
		}
		if (activeConnections != null){
			try {
				//only connect Device, I not already connected to or a connection is open or failed
				marc1: 
				for (BluetoothDeviceAddress addy : this.inquiryCache){
					if (this.connectionState.get(addy.getAddress()) == null && !this.rT.isRouteKnown(addy)){
						this.connectionState.put(addy.getAddress(), connection_state.OPEN);
						super.rfcomm_connect_to_server(addy);
						break marc1;
					}
				}
			} catch (ScatterNetException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * This Method asks the as connectedDevice-Parameter given Device for his Routing-Table Informations
	 * 
	 * @param connectedDevice
	 * General Contruction of Messages
	 * Message
	 * 		|
	 * 		-->Content = Routing-Object
	 * 								|
	 * 								-->Vector<Object>
	 * 											|
	 * 											--> 1. Message_Type (have a look to the local enum)
	 * 											--> 2. Parameter 1 (if used by Disc Mechanism then this is a Discovery Message)
	 * 											--> 3... Further individual needed Parameters
	 * 
	 * Special Construcion of Messages
	 * Message
	 * 		|
	 * 		-->Content = Routing-Object
	 * 								|
	 * 								-->Vector<Object>
	 * 											|
	 * 											--> 1. Message_Type.ROUTING_INFORMATION_QUERY
	 * 											--> 2. connectedDevice (the to Network connected Device to ask)
	 * 
	 */
	private void askForRoutingTableInformations (BluetoothDeviceAddress connectedDevice){
		Vector<Object> rO_content = new Vector<Object>();
		rO_content.add(message_type.ROUTING_INFORMATION_QUERY);
		rO_content.add(connectedDevice);
		//Answer, than target becomes clear Information
		RoutingObject newRO = new RoutingObject(connectedDevice, rO_content);
		newRO.addHopToRoute(this.host.getBd_Addr());
		Message tmpMsg = new Message(newRO, false);
		super.invokeSendMessage(tmpMsg, rT.getNextHop(connectedDevice));
	}

	/**
	 * This Method sends if a Device connect to me, this information to all other devices. So every Device
	 * should be able to update his Routing-Table
	 * 
	 * 
	 * @param remoteHost
	 * 
	 * General Contruction of Messages
	 * Message
	 * 		|
	 * 		-->Content = Routing-Object
	 * 								|
	 * 								-->Vector<Object>
	 * 											|
	 * 											--> 1. Message_Type (have a look to the local enum)
	 * 											--> 2. Parameter 1 (if used by Disc Mechanism then this is a Discovery Message)
	 * 											--> 3... Further individual needed Parameters
	 * 
	 * Special Construcion of Messages
	 * Message
	 * 		|
	 * 		-->Content = Routing-Object
	 * 								|
	 * 								-->Vector<Object>
	 * 											|
	 * 											--> 1. Message_Type.NEW_DEVICE_CONNECTED
	 * 											--> 2. remoteHost (the new to Network connected Host)
	 * 											--> 3. HashMap<BluetoothDeviceAddress, Vector<BluetoothDeviceAddress>> (routingTable Information from new Connected host)
	 * @param routingTableInfo 
	 * 
	 */
	private void sendNewDeviceConnectedToNeighbourhood(BluetoothDeviceAddress remoteHost, HashMap<BluetoothDeviceAddress, Vector<BluetoothDeviceAddress>> routingTableInfo) {
		if (rT != null){
			for (BluetoothDeviceAddress neighbour : rT.getNeighbors()){
				Vector<Object> rO_content = new Vector<Object>();
				rO_content.add(message_type.NEW_DEVICE_CONNECTED);
				rO_content.add(remoteHost);
				rO_content.add(routingTableInfo);
				//Answer, than target becomes clear Information
				RoutingObject newRO = new RoutingObject(neighbour, rO_content);
				newRO.addHopToRoute(this.host.getBd_Addr());
				Message tmpMsg = new Message(newRO, false);
				super.invokeSendMessage(tmpMsg, rT.getNextHop(neighbour));
			}
		}
	}

	/**
	 * 
	 * @param initialSender
	 * 
	 * General Contruction of Messages
	 * Message
	 * 		|
	 * 		-->Content = Routing-Object
	 * 								|
	 * 								-->Vector<Object>
	 * 											|
	 * 											--> 1. Message_Type (have a look to the local enum)
	 * 											--> 2. Parameter 1 (if used by Disc Mechanism then this is a Discovery Message)
	 * 											--> 3... Further individual needed Parameters
	 * 
	 * Special Construcion of Messages
	 * Message
	 * 		|
	 * 		-->Content = Routing-Object
	 * 								|
	 * 								-->Vector<Object>
	 * 											|
	 * 											--> 1. Message_Type.RESP_ROUTING_INFORMATION_QUERY
	 * 											--> 2. HashMap<BluetoothDeviceAddress, Vector<BluetoothDeviceAddress>> routingTableRoutes
	 * 
	 */
	private void sendRoutingTableInformations(BluetoothDeviceAddress initialSender) {
			Vector<Object> rO_content = new Vector<Object>();
			rO_content.add(message_type.RESP_ROUTING_INFORMATION_QUERY);
			rO_content.add(rT.getAllKnownRoutes());
			//Answer, than target becomes clear Information
			RoutingObject newRO = new RoutingObject(initialSender, rO_content);
			newRO.addHopToRoute(this.host.getBd_Addr());
			Message tmpMsg = new Message(newRO, false);
			super.invokeSendMessage(tmpMsg, rT.getNextHop(initialSender));
	}
	
	public Vector<BluetoothDeviceAddress> getCurrentConnections (){
		return rfcomm_show();
	}
	
	public boolean isDeviceStillConnected(){
		return rfcomm_show().size() > 0;
	}
	
	public Vector<BluetoothDeviceAddress> getAllAvailableDevices (){
		if (rT == null){
			return new Vector<BluetoothDeviceAddress>();
		}
		return rT.getAllAvailableDestinations();
	}

	class RoutingTable {
		//Target Bluetoothdeviceadress --> neighbour to which to send
		private HashMap <String, BluetoothDeviceAddress> nextHop = new HashMap <String, BluetoothDeviceAddress>();
		//Target BluetoothdeviceAdress --> all Neighbours, to which i can send
		private HashMap <String, Vector<BluetoothDeviceAddress>> routeList = new HashMap <String, Vector<BluetoothDeviceAddress>>();
		//Target-BluetoothdevicesAddress --> Target Costs at the moment for sending a Message
		private HashMap <String, Integer> distanceVector = new HashMap <String, Integer>();
		//Simple List of all direct connected Devices
		private Vector<BluetoothDeviceAddress> directNeighbor = new Vector<BluetoothDeviceAddress>();
		//Target-BluetoothDevicesAddress as String --> really BluetoothDeviceAddress
		private HashMap <String, BluetoothDeviceAddress> stringToBDA = new HashMap<String, BluetoothDeviceAddress>();
		
		public BluetoothDeviceAddress getNextHop(BluetoothDeviceAddress receiver){
			if (this.stringToBDA.get(receiver.getAddress()) != null){
				return nextHop.get(receiver.getAddress());				
			}
			return null;
		}
		
		public void saveNewDevice(BluetoothDeviceAddress newNode,int costs, BluetoothDeviceAddress sender) {
			if (this.stringToBDA.get(newNode.getAddress()) == null){
				//--stringToBDA
				this.stringToBDA.put(newNode.getAddress(), newNode);
				//--routeList
				Vector<BluetoothDeviceAddress> overnodes = new Vector<BluetoothDeviceAddress>();
				overnodes.add(nextHop.get(sender.getAddress()));
				this.routeList.put(newNode.getAddress(), overnodes);
				//--distanceVector
				this.distanceVector.put(newNode.getAddress(), new Integer(costs));
				//--nextHop
				this.nextHop.put(newNode.getAddress(), this.nextHop.get(sender.getAddress()));
			} else {
				if (!(this.distanceVector.get(newNode.getAddress()) < costs)){
					//--routeList
					Vector<BluetoothDeviceAddress> overnodes = routeList.get(sender.getAddress());
					overnodes.add(nextHop.get(sender.getAddress()));
					//--distanceVector
					this.distanceVector.put(newNode.getAddress(), new Integer(costs));
					//--nextHop
					this.nextHop.put(newNode.getAddress(), this.nextHop.get(sender.getAddress()));					
				} else {
					if (this.nextHop.get(newNode.getAddress()).isEqual(sender)){
							//--routeList
							Vector<BluetoothDeviceAddress> overnodes = this.routeList.get(newNode.getAddress());
							overnodes.add(nextHop.get(sender.getAddress()));
					}
				}
			}
		}

		@SuppressWarnings("unchecked")
		public void updateWithRouteInformation(BluetoothDeviceAddress dev, HashMap<BluetoothDeviceAddress, Integer> hashMap) {
			HashMap<BluetoothDeviceAddress, Integer> tmp =  (HashMap<BluetoothDeviceAddress, Integer>) hashMap.clone();
			for (BluetoothDeviceAddress addy: tmp.keySet()){
				if (stringToBDA.get(addy.getAddress()) == null){
					//--stringToBDA
					this.stringToBDA.put(addy.getAddress(), addy);
					//--routeList
					Vector<BluetoothDeviceAddress> overnodes = new Vector<BluetoothDeviceAddress>();
					overnodes.add(nextHop.get(dev.getAddress()));
					this.routeList.put(addy.getAddress(), overnodes);
					//--distanceVector
					this.distanceVector.put(addy.getAddress(), new Integer(tmp.get(addy).intValue() + this.distanceVector.get(dev.getAddress())));
					//--nextHop
					this.nextHop.put(addy.getAddress(), this.nextHop.get(dev.getAddress()));
				} 
				//Device is already known
				else {
					//only update if theres an advantage
					if (this.distanceVector.get(addy.getAddress()) < (tmp.get(addy).intValue() + this.distanceVector.get(dev.getAddress()))){
						//--routeList
						Vector<BluetoothDeviceAddress> overnodes = this.routeList.get(addy.getAddress());
						overnodes.add(nextHop.get(dev.getAddress()));
						//--distanceVector
						this.distanceVector.put(addy.getAddress(), new Integer(tmp.get(addy).intValue() + this.distanceVector.get(dev.getAddress())));
						//--nextHop
						this.nextHop.put(addy.getAddress(), this.nextHop.get(dev.getAddress()));
					}
					//if theres no advantage, its only intresting, that theres a new way to reach the device
					else {
						if (!this.nextHop.get(addy.getAddress()).isEqual(this.nextHop.get(dev.getAddress()))){
							//--routeList
							Vector<BluetoothDeviceAddress> overnodes = this.routeList.get(addy.getAddress());
							overnodes.add(nextHop.get(dev.getAddress()));
						}
					}
				}
			}
		}

		/**
		 * within an Routing Message an Routing-List came from the device
		 * with bd_addr and the Message comes along the way "route"
		 * --> distance from bd_addr is 1 Hop
		 * --> distance from source is 1 + routelength
		 * --> route is neighbour=last element -> 1 hop = last element - 1 -> ...
		 * 
		 * the DV will beUpdated, if the Route is shorter than the known
		 */
		public void updateDistanceVector (Vector<BluetoothDeviceAddress> route){
			if (!route.lastElement().isEqual(host.getBd_Addr())){
				//add bd_addr to neighbourhood-
				if (stringToBDA.get(route.lastElement().getAddress()) == null){
					this.addNewDeviceToRoutingTable(route.lastElement(), null);
				} else {
					//if new way is shorter oldvalue < newvalue = last - i
					if (distanceVector.get(route.lastElement().getAddress()).intValue() > 1){
						this.exchangeDeviceInRoutingTable(route.lastElement(), null);
					}
				}
				for (int i = route.size()-2; i >= 0; i--){
						if(stringToBDA.get(route.get(i).getAddress()) == null){
							this.addNewDeviceToRoutingTable(route.get(i), route.get(i+1));
						} else {
							//if new way is shorter oldvalue < newvalue = last - i
							if (distanceVector.get(route.get(i).getAddress()).intValue() > (route.size()-1)-i){
								this.exchangeDeviceInRoutingTable(route.get(i), route.get(i+1));
							}
						}
				}				
			} 
		}
		
		
		/**
		 * This method is for update the Routing-Table. First Parameter is the new Host and second Parameter is the connected Neighbor over which the Message should be send.
		 * @param exchangeEntry
		 * @param overNode
		 */
		private void exchangeDeviceInRoutingTable (BluetoothDeviceAddress exchangeEntry, BluetoothDeviceAddress overNode){
				this.distanceVector.remove(exchangeEntry.getAddress());
				this.nextHop.remove(exchangeEntry.getAddress());
				this.routeList.remove(exchangeEntry.getAddress());
				this.directNeighbor.remove(exchangeEntry);
				this.addNewDeviceToRoutingTable(exchangeEntry, overNode);
		}
		/**
		 * overNode must be known , if it is null new Neighbor is added
		 * 
		 * @param newEntry 
		 * @param overNode should be known or is null if it is a new Neighbor
		 */
		@SuppressWarnings("unchecked")
		private void addNewDeviceToRoutingTable (BluetoothDeviceAddress newEntry, BluetoothDeviceAddress overNode){
			if (overNode == null){
				nextHop.put(newEntry.getAddress(), newEntry);
				distanceVector.put(newEntry.getAddress(), new Integer(1));
				Vector<BluetoothDeviceAddress> route = new Vector<BluetoothDeviceAddress>();
				route.add(newEntry);
				routeList.put(newEntry.getAddress(), route);
				this.directNeighbor.add(newEntry);
				this.stringToBDA.put(newEntry.getAddress(), newEntry);
			} else {
				nextHop.put(newEntry.getAddress(), nextHop.get(overNode.getAddress()));
				distanceVector.put(newEntry.getAddress(), new Integer(distanceVector.get(overNode.getAddress()).intValue() + 1));
				Vector<BluetoothDeviceAddress> route = new Vector<BluetoothDeviceAddress>();
				route = (Vector<BluetoothDeviceAddress>)routeList.get(overNode.getAddress()).clone();
				route.add(overNode);
				routeList.put(newEntry.getAddress(), route);
				this.stringToBDA.put(newEntry.getAddress(), newEntry);
			}
		}
		
		public Vector<BluetoothDeviceAddress> getNeighbors (){
			return this.directNeighbor;
		}
		
		/**
		 * This method is for asking for an Host, and returns true if a route to the destination is known
		 * @return
		 */
		public boolean isRouteKnown (BluetoothDeviceAddress destination){
			return this.stringToBDA.get(destination.getAddress()) != null;
		}
		
		/**
		 * for getting an List of all Devices reachable over network (that stored in the Routing Table)
		 * @return
		 */
		@SuppressWarnings("unchecked")
		public HashMap<BluetoothDeviceAddress, Integer> getAllKnownRoutes(){
			HashMap<BluetoothDeviceAddress, Integer> tmp_ret = new HashMap<BluetoothDeviceAddress, Integer>();
			for (String key : stringToBDA.keySet()){
				tmp_ret.put(stringToBDA.get(key), distanceVector.get(key));
			}
			return tmp_ret;
		}
		
		public int getNumberOfHops (BluetoothDeviceAddress target){
			if (this.distanceVector.get(target.getAddress()) == null || this.routeList.get(this.distanceVector.get(target.getAddress())) == null){
				return -1;
			} else {
				return this.routeList.get(this.distanceVector.get(target.getAddress())).size();
			}
		}
		
		private int getNumberOfHops (String target){
			if (this.distanceVector.get(target) == null || this.routeList.get(this.distanceVector.get(target)) == null){
				return -1;
			} else {
				return this.routeList.get(this.distanceVector.get(target)).size();
			}
		}
		
		public void removeHostFromRoutingTable (BluetoothDeviceAddress device){
			this.stringToBDA.remove(device.getAddress());
		}
		
		public Vector<BluetoothDeviceAddress> getAllAvailableDestinations (){
			return new Vector<BluetoothDeviceAddress> (this.stringToBDA.values());
		}
	}
	
	
	private	class RoutingObject {
		
		//Message Receiver 
		private BluetoothDeviceAddress target = null;
		//Message that should be transmitted to target
		private Vector<Object> message= null;
		
		//hopRoute Between Devices
		private Vector<BluetoothDeviceAddress> route = new Vector<BluetoothDeviceAddress>();
		//the type of a message is the explicit property of the Message
		//for example it can be used to compare with special "SEARCH PHRASE" etc.
		
		public Vector<BluetoothDeviceAddress> getRoute (){
			return this.route;
		}
		
		/**
		 * Konstruktor ohne Reason
		 * @param target --> finally Receiver
		 * @param msg
		 */
		public RoutingObject(BluetoothDeviceAddress target, Vector<Object> msg){
			this.message = msg;
			this.target = target;
		}
		
		
		public void addHopToRoute (BluetoothDeviceAddress hop){
			this.route.add(hop);
		}
		
		public Vector<Object> getMessage(){
			return this.message;
		}
		
		
		public BluetoothDeviceAddress getTarget (){
			return this.target;
		}
		
		public BluetoothDeviceAddress getInitialSender(){
			return this.route.firstElement();
		}
		
		public void setRoute (Vector<BluetoothDeviceAddress> route){
			this.route = route;
		}
	}

}
